// std
#include <sstream>

// boost
#include <boost/bind.hpp>

// editor
#include "TextScreen.h"
#include "ScreenGDIInfo.h"
#include "MainWindow.h"
#include "editorutil.h"

namespace editor {

using namespace std;

// OutTextScreen {{{

OutTextScreen::OutTextScreen(int id, int customID, HWND hwnd, HDC parentDC,
        int x, int y,
        int width, int height,
        int minX, int minY,
        int offX, int offY
        ) :
    AbstractScreen(id, x, y, width, height, minX, minY, offX, offY),
    customID_(customID),
    hWnd_(hwnd),
    parentDC_(parentDC),
    cinfo_(g_cinfo),
    finfo_(g_finfo)
{
}

OutTextScreen::~OutTextScreen() {
}

bool OutTextScreen::sendKey(short key, bool repeat) {
    return false;
}

void OutTextScreen::updateSize(int x, int y, int width, int height) {
    AbstractScreen::updateSize(x, y, width, height);
}

void OutTextScreen::update() {
    updateState_.request(OTXT_SCR_ALL);
    tryUpdate();
}

void OutTextScreen::draw() {
}

bool OutTextScreen::tryUpdate() {
    if (!updateState_.isRequested(OTXT_SCR_BACKGROUD)) return false;

    ScopedBrush brush(cinfo_.colorRef(EDTR_COLOR_BACKGROUND));
    parentDC_.select(brush);
    PatBlt(parentDC_.get(), x_, y_, width_, height_, PATCOPY);
    int oldmode = SetBkMode(parentDC_.get(), TRANSPARENT);
    const FontInfo& finfo = finfo_.fontInfo(EDTR_FONT_LABEL);
    SetTextColor(parentDC_.get(), finfo.color());
    ScopedFont font(finfo);
    parentDC_.select(font);
    parentDC_.textout(x_, y_, text_);
    if (oldmode) {
        SetBkMode(parentDC_.get(), oldmode);
    }

    updateState_.end(OTXT_SCR_ALL);
    return true;
}

void OutTextScreen::setText(const std::string& str) {
    text_ = str;
    updateState_.request(OTXT_SCR_BACKGROUD);
}

std::string OutTextScreen::text() const {
    return text_;
}

// OutTextScreen }}}

// InTextScreen {{{

InTextScreen::InTextScreen(int id, int customID, HWND hwnd, HDC parentDC,
        int x, int y,
        int width, int height,
        int minX, int minY,
        int offX, int offY
        ) :
    AbstractScreen(id, x, y, width, height, minX, minY, offX, offY),
    customID_(customID),
    hWnd_(hwnd),
    parentDC_(parentDC),
    keypos_(0),
    cinfo_(g_cinfo),
    finfo_(g_finfo)
{
    setAction(E_INPUT_MODE,        EventAction(bind(&InTextScreen::inputMode, this)));
    setAction(E_ENTER_TEXT,        EventAction(bind(&InTextScreen::inputText, this)));
    setAction(E_ESCAPE_INPUT_MODE, EventAction(bind(&InTextScreen::escapeInputMode, this)));
    setAction(E_BACK_INPUT,        EventAction(bind(&InTextScreen::back, this)));
    setAction(E_FORWARD_INPUT,     EventAction(bind(&InTextScreen::forward, this)));
    setAction(E_BACKSPACE_INPUT,   EventAction(bind(&InTextScreen::backspace, this)));

    setKey(translate('i'), E_INPUT_MODE);

    setKeyForInputMode(translate('m', true, false, false, true),         E_ENTER_TEXT);
    setKeyForInputMode(translate(VK_RETURN, false, false, false, false), E_ENTER_TEXT);
    setKeyForInputMode(translate('[', true, false, false, true),         E_ESCAPE_INPUT_MODE);
    setKeyForInputMode(translate(VK_ESCAPE, false, false, false, false), E_ESCAPE_INPUT_MODE);
    setKeyForInputMode(translate(VK_BACK, false, false, false, false),   E_BACKSPACE_INPUT);
    setKeyForInputMode(translate('h', true, false, false, true),         E_BACKSPACE_INPUT);
    setKeyForInputMode(translate(VK_LEFT, false, false, false, false),   E_BACK_INPUT);
    setKeyForInputMode(translate('k', true, false, false, true),         E_BACK_INPUT);
    setKeyForInputMode(translate(VK_RIGHT, false, false, false, false),  E_FORWARD_INPUT);
    setKeyForInputMode(translate('j', true, false, false, true),         E_FORWARD_INPUT);
}

InTextScreen::~InTextScreen() {
}

bool InTextScreen::sendKey(short key, bool repeat) {
    executeKey(key);
    return tryUpdate();
}

int InTextScreen::sendKeyForInputMode(short key, bool repeat) {
    int flg = 0;
    if (!executeKeyForInputMode(key)) {
        flg |= 0x02;
    }
    if (tryUpdate()) {
        flg |= 0x01;
    }
    return flg;
}

void InTextScreen::updateSize(int x, int y, int width, int height) {
    AbstractScreen::updateSize(x, y, width, height);
}

void InTextScreen::update() {
    updateState_.request(ITXT_SCR_ALL);
    tryUpdate();
}

void InTextScreen::draw() {
}

bool InTextScreen::tryUpdate() {
#if 1
    if (g_window.mode() == MODE_INPUT && isFocused()) {
        if (!updateState_.isRequested(ITXT_SCR_POSMARK)) return false;
        ScopedBrush brush(cinfo_.colorRef(EDTR_COLOR_BACKGROUND));
        parentDC_.select(brush);
        PatBlt(parentDC_.get(), x_, y_, width_, height_, PATCOPY);
        int oldmode = SetBkMode(parentDC_.get(), TRANSPARENT);
        const FontInfo& finfo = finfo_.fontInfo(EDTR_FONT_INPUT_TEXT);
        ScopedFont font(finfo);
        parentDC_.select(font);
        SetTextColor(parentDC_.get(), finfo.color());
        parentDC_.textout(x_, y_, inText_);
        if (oldmode) {
            SetBkMode(parentDC_.get(), oldmode);
        }

        // position mark
        ScopedBrush posbrush(cinfo_.colorRef(EDTR_COLOR_TEXT_POSMARK));
        parentDC_.select(posbrush);
        SIZE sz, textSz;
        int left, top, width, height;
        if (keypos_ == inText_.size()) {
            if (inText_.empty()) {
                if (GetTextExtentPoint32(parentDC_.get(), " ", 1, &sz)) {
                    left = 0, top = 0, width = sz.cx, height = sz.cy;
                    PatBlt(parentDC_.get(), x_+left, y_+top, width, height, PATCOPY);
                }
            } else if (GetTextExtentPoint32(parentDC_.get(), " ", 1, &sz)
                    && GetTextExtentPoint32(parentDC_.get(), inText_.c_str(), inText_.size(), &textSz)) {
                left = textSz.cx+1, top = 0, width = sz.cx, height = sz.cy;
                PatBlt(parentDC_.get(), x_+left, y_+top, width, height, PATCOPY);
            }
        } else {
            string subline = inText_.substr(0, keypos_);
            if (subline.empty()) {
                if (GetTextExtentPoint32(parentDC_.get(), " ", 1, &sz)) {
                    left = 0, top = 0, width = 2, height = sz.cy;
                    PatBlt(parentDC_.get(), x_+left, y_+top, width, height, PATCOPY);
                }
            } else if (GetTextExtentPoint32(parentDC_.get(), subline.c_str(), subline.size(), &textSz)) {
                left = textSz.cx, top = 0, width = 2, height = textSz.cy;
                PatBlt(parentDC_.get(), x_+left, y_+top, width, height, PATCOPY);
            }
        }
    } else {
        if (!updateState_.isRequested(ITXT_SCR_BACKGROUD)) return false;
        ScopedBrush brush(cinfo_.colorRef(EDTR_COLOR_BACKGROUND));
        parentDC_.select(brush);
        PatBlt(parentDC_.get(), x_, y_, width_, height_, PATCOPY);
        int oldmode = SetBkMode(parentDC_.get(), TRANSPARENT);
        const FontInfo& finfo = finfo_.fontInfo(EDTR_FONT_TEXT);
        ScopedFont font(finfo);
        parentDC_.select(font);
        SetTextColor(parentDC_.get(), finfo.color());
        parentDC_.textout(x_, y_, text_);
        if (oldmode) {
            SetBkMode(parentDC_.get(), oldmode);
        }
    }
#else
    ScopedBrush brush(cinfo_.colorRef(EDTR_COLOR_BACKGROUND));
    parentDC_.select(brush);
    PatBlt(parentDC_.get(), x_, y_, width_, height_, PATCOPY);
    int oldmode = SetBkMode(parentDC_.get(), TRANSPARENT);
    const FontInfo& finfo = finfo_.fontInfo(EDTR_FONT_TEXT);
    ScopedFont font(finfo);
    parentDC_.select(font);
    SetTextColor(parentDC_.get(), finfo.color());
    ostringstream ost;
    ost << x_ << ":" << y_;
    parentDC_.textout(x_, y_, ost.str());
    if (oldmode) {
        SetBkMode(parentDC_.get(), oldmode);
    }
#endif
    updateState_.end(ITXT_SCR_ALL);
    return true;
}

std::string InTextScreen::text() const {
    return text_;
}

bool InTextScreen::inputKey(char key) {
//    inText_.append(1, key);
    inText_.insert(inText_.begin() + keypos_, key);
    ++keypos_;
    setEvent(ravi::make_val(new ravi::str_t(inText_)));
    updateState_.request(ITXT_SCR_POSMARK);
    return true; 
}

bool InTextScreen::inputMode() {
    inText_ = text_;
    keypos_ = inText_.size();
    g_window.enterInputMode();
    updateState_.request(ITXT_SCR_BACKGROUD);
    return true;
}

bool InTextScreen::inputText() {
    setEvent(ravi::make_val(new ravi::str_t(inText_)));
    if (userExecute(E_ENTER_TEXT)) {
        text_ = inText_;
    }
    g_window.exitInputMode();
    updateState_.request(ITXT_SCR_ALL);
    return true;
}

bool InTextScreen::escapeInputMode() {
    inText_ = string();
    keypos_ = 0;
    userExecute(E_ESCAPE_INPUT_MODE);
    g_window.exitInputMode();
    updateState_.request(ITXT_SCR_ALL);
    return true;
}

bool InTextScreen::back() {
    if (keypos_ > 0) {
        --keypos_;
        updateState_.request(ITXT_SCR_POSMARK);
    }
    return false;
}

bool InTextScreen::forward() {
    if (keypos_ < inText_.size()) {
        ++keypos_;
        updateState_.request(ITXT_SCR_POSMARK);
    }
    return false;
}

bool InTextScreen::backspace() {
    if (!inText_.empty() && keypos_ > 0) {
        --keypos_;
        inText_.erase(inText_.begin() + keypos_);
        updateState_.request(ITXT_SCR_POSMARK);
    }
    return false;
}

void InTextScreen::setText(const std::string& str) {
    text_ = str;
    updateState_.request(ITXT_SCR_BACKGROUD);
}

// InTextScreen }}}

} // end of namespace editor
